﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using AmiEmuLib.Native;
using System.Windows.Forms;
using System.Diagnostics;
using System.Runtime.InteropServices;
using log4net;

namespace AmiEmuLib.Host.HostInput
{
    /// <summary>
    /// Keyboards manager.
    /// </summary>
    public class HostKeyboardsDevices : HostInputDevices<HostKeyboardDevice>
    {
        /// <summary>
        /// Logger.
        /// </summary>
        private static readonly ILog Logger = LogManager.GetLogger("AmiEmuLib.Host.Input.Keyboard");

        /// <summary>
        /// Constructor.
        /// </summary>
        internal HostKeyboardsDevices()
        {
        }

        /// <summary>
        /// Starts listening to WM_INPUT for all keyboards class devices. 
        /// </summary>
        internal override void Connect()
        {
            NativeRawInput.RawInputDevice[] rid = new NativeRawInput.RawInputDevice[1];

            rid[0].UsagePage = NativeConsts.HIDUsagePage_Generic;
            rid[0].Usage = NativeConsts.HIDUsage_Keyboard;
            rid[0].Flags = NativeRawInput.RawInputDeviceFlags.InputSink | 
                NativeRawInput.RawInputDeviceFlags.NoHotKeys;
            rid[0].WindowHandle = HostEnvironment.WindowHandle;

            if (!NativeRawInput.RegisterRawInputDevices(
                rid, 1, NativeRawInput.RawInputDevice.StructSize))
            {
                int error = Marshal.GetLastWin32Error();
                Logger.ErrorFormat("RegisterRawInputDevices() failed for keyboards reg (error: {0})",
                    error);
            }
        }

        /// <summary>
        /// Stop listening to WM_INPUT for all keyboards class devices. 
        /// </summary>
        internal override void Disconnect()
        {
            NativeRawInput.RawInputDevice[] rid = new NativeRawInput.RawInputDevice[1];

            rid[0].UsagePage = NativeConsts.HIDUsagePage_Generic;
            rid[0].Usage = NativeConsts.HIDUsage_Keyboard;
            rid[0].Flags = NativeRawInput.RawInputDeviceFlags.Remove;

            if (!NativeRawInput.RegisterRawInputDevices(
                rid, 1, NativeRawInput.RawInputDevice.StructSize))
            {
                int error = Marshal.GetLastWin32Error();
                Logger.ErrorFormat("RegisterRawInputDevices() failed for keyboards unreg (error: {0})",
                    error);
            }
        }

        /// <summary>
        /// Log raw keyboboard struct.
        /// </summary>
        /// <param name="keyboard"></param>
        private void LogRawData(ref NativeRawInput.RawKeyboard keyboard)
        {
            Logger.InfoFormat("flags: {0:X}, makecode: {1:X}, message: {2:X}, virtualkey: {3:X}, reserved: {4:X}, extra: {5:X}",
                (ushort)keyboard.Flags, (ushort)keyboard.MakeCode, (uint)keyboard.Message,
                (ushort)keyboard.VirtualKey, (ushort)keyboard.Reserved,
                (uint)keyboard.ExtraInformation);
        }

        /// <summary>
        /// Process raw input events for keyboards. Called from main window message loop.
        /// </summary>
        /// <param name="a_focus"></param>
        /// <param name="a_ri"></param>
        internal void ProcessRawInputMessage(bool a_focus, NativeRawInput.RawInput a_ri)
        {
            NativeRawInput.RawKeyboard keyboard = a_ri.Data.Keyboard;

            if (Logger.IsDebugEnabled)
                LogRawData(ref keyboard);

            if (((ushort)keyboard.Flags & ~7) != 0)
            {
                if (Logger.IsDebugEnabled)
                {
                    Logger.Error("Some unexcpected flags");
                    LogRawData(ref keyboard);
                }
            }

            if ((keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE0)) &&
                (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE1)))
            {
                Logger.Error("EO and E1 both set.");
                LogRawData(ref keyboard);
            }

            if (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE1))
            {
                Logger.Error("E1 set.");
                LogRawData(ref keyboard);
            }

            if (keyboard.MakeCode == NativeRawInput.KEYBOARD_OVERRUN_MAKE_CODE)
            {
                Logger.Info("Keyboard overrun make code.");
                LogRawData(ref keyboard);
                return;
            }

            bool pressed = !keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyBreak);

            if (!a_focus && pressed)
                return;

            int key = (int)keyboard.VirtualKey;

            switch (key)
            {
                case (int)Keys.ShiftKey:
                {
                    if (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE0))
                    {
                        Logger.Error("Shift EO.");
                        LogRawData(ref keyboard);
                    }
                    if (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE1))
                    {
                        Logger.Error("Shift E1.");
                        LogRawData(ref keyboard);
                    }

                    if ((keyboard.MakeCode != 0x2A) && (keyboard.MakeCode != 0x36))
                    {
                        Logger.Error("Shift bad scan code.");
                        LogRawData(ref keyboard);
                    }

                    key = (int)NativeRawInput.MapVirtualKey(
                        (uint)keyboard.MakeCode, NativeRawInput.MapVirtualKeyEnum.MAPVK_VSC_TO_VK_EX);

                    if (key == 0)
                    {
                        Logger.Error("MapVirtualKey() failed to find left right shift.");
                        LogRawData(ref keyboard);
                        key = (int)Keys.LShiftKey;
                    }

                    break;
                }
                case (int)Keys.ControlKey:
                {
                    if (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE1))
                    {
                        Logger.Error("Ctrl E1.");
                        LogRawData(ref keyboard);
                    }

                    if (keyboard.MakeCode != 0x1D)
                    {
                        Logger.Error("Ctrl bad scan code.");
                        LogRawData(ref keyboard);
                    }

                    if (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE0))
                        key = (int)Keys.RControlKey;
                    else
                        key = (int)Keys.LControlKey;

                    break;
                }
                case (int)Keys.Menu:
                {
                    if (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE1))
                    {
                        Logger.Error("Alt E1.");
                        LogRawData(ref keyboard);
                    }

                    if (keyboard.MakeCode != 0x38)
                    {
                        Logger.Error("Alt bad scan code.");
                        LogRawData(ref keyboard);
                    }

                    if (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE0))
                        key = (int)Keys.RMenu;
                    else
                        key = (int)Keys.LMenu;

                    break;
                }
                case 0:
                {
                    Logger.Error("virtual key code is 0x00");
                    LogRawData(ref keyboard);
                    return;
                }
                case 255:
                {
                    if (Logger.IsDebugEnabled)
                    {
                        Logger.Debug("virtual key code is 0xFF");
                        LogRawData(ref keyboard);
                    }
                    return;
                }
                case (int)Keys.Return:
                {
                    if (keyboard.Flags.HasFlag(NativeRawInput.RawKeyboardFlags.KeyE0))
                        key = 197;

                    break;
                }
            }

            if ((key > 255) || (key < 0))
            {
                Logger.ErrorFormat("Unexpected key value: {0}", key);
                LogRawData(ref keyboard);
                return;
            }

            if (a_focus || m_states[key])
            {
                m_states[key] = pressed;
                GenerateEvent(key, pressed, key);
            }
        }

        protected override HostEvents GetHostEvents()
        {
            return HostEventsList.KeyboardEvents;
        }
    }
}
